package org.amse.korovkin.ciphering.model;
import org.amse.korovkin.ciphering.model.cipher.monocipher.MonoAttacker;
import org.amse.korovkin.ciphering.model.cipher.monocipher.ChangeTable;
import java.util.HashSet;
import java.util.zip.DataFormatException;
import org.amse.korovkin.ciphering.Main;

public class PossibilityTable {
    private static final double VERY_SMALL_THRESHOLD = 0.95;//0.95   0.5%>=th
    private static final double SMALL_THRESHOLD = 0.75;//0.75          1%>=th>0.5%
    private static final double SMALL_AVERAGE_THRESHOLD = 0.30;//0.3 1.8%>=th>1%
    private static final double SMALL_BIG_THRESHOLD = 0.40; //0.40     2%>=th>1.8%
    private static final double AVERAGE_THRESHOLD = 0.2;  //0.2        5%>=th>2%
    private static final double BIG_AVERAGE_THRESHOLD = 0.1; //0.12    7%>=th>5%
    private static final double BIG_THRESHOLD = 0.15; //0.2                th>7%
    public static HashSet<Character> ALPHABET = new HashSet<Character>();
    // Full Amount of all symbols
    private int myFullAmount;
    // Amount of letters from {'a', ..., 'z', ' '} that must be exchange to candidats
    private int myAmount;
    // Candidats to exchange the letter
    private int[][] myPossibility = new int[Main.NUM_LETTERS][Main.NUM_LETTERS];
    // Letter that must be exchanged to candidat
    private char[] myChar = new char[Main.NUM_LETTERS];
    // Frequency of letter that must be exchanged
    private double[] myFrequency = new double[Main.NUM_LETTERS];
    // Amount of candidats to one letter
    private int[] myNum = new int[Main.NUM_LETTERS];
    private ChangeTable buf;
    private long counter;

    public PossibilityTable() {
    }
    /**
     * Remember Letters that must be exchanged, their Frequencies and Amount of Letters.
     * @param table Table of Frequencies of Letters
     */
    public PossibilityTable(TableOfFrequency table) {
        myFullAmount = table.getNum();
        char[] ch = table.getLetters();
        double[] fr = table.getFrequencies();
        for (int i = 0; i < myFullAmount; i++) {
            if ((ch[i] >= 'a' && ch[i] <= 'z') || ch[i] == ' ') {
                myChar[myAmount] = ch[i];
                myFrequency[myAmount] = fr[i];
                myAmount++;
            }
        }
        int buf = myAmount;
        for (int i = 0; i < myFullAmount; i++) {
            if ((ch[i] < 'a' || ch[i] > 'z') && ch[i] != ' ') {
                myChar[buf] = ch[i];
                myFrequency[buf] = fr[i];
                buf++;
            }
        }
    }
    /**
     * Return Candidat to letter with index {@code i} and number {@code j}
     * @param i Number of line
     * @param j Number of column (number in line)
     * @return Integer value of Candidat with descripted indexes
     */
    public int getPossibility(int i, int j) {
        return myPossibility[i][j];
    }
    /**
     * Return Letter with index of line {@code i} that must be exchanged
     * @param i Index of line
     * @return Integer of Letter
     */
    public int getChar(int i) {
        return myChar[i];
    }
    /**
     * Return Amount of Candidats to Letter with index {@code i}
     * @param i Index of line
     * @return Integer - Amount of Candidats
     */
    public int getLine(int i) {
        return myNum[i];
    }
    /**
     * Return Amount of Letters that must be exchanged
     * @return Amount of Letters
     */
    public int getAmount() {
        return myAmount;
    }
    
    /**
     * Add new Candidat for line with index {@code i}.
     * @param i Index of line
     * @param possibility - Candidat for exchange
     */
    public void addPossibility(int i, int possibility) {
        myPossibility[i][this.getLine(i)] = possibility;
        myNum[i]++;
    }
    public void setPossibility(int i, int j, char possibility) {
        myPossibility[i][j] = possibility;
    }
    public void deletePossibility (int i, int j) {
        if (i <= myAmount && j <= getLine(i)) {            
            for (int k = j; k < getLine(i)-1; k++) {
                myPossibility[i][k] = myPossibility[i][k+1];
            }
            myPossibility[i][myNum[i] - 1] = 0;
            myNum[i]--;
        }        
    }

    // return Threshold in (persents / 100) of Letter that must be exchange to Candidat's Frequency
    private double getThreshold(int i) {
        if (myFrequency[i] > 0.07) {
            return BIG_THRESHOLD;
        } else {
            if (myFrequency[i] > 0.05) {
                return BIG_AVERAGE_THRESHOLD;
            } else {
                if (myFrequency[i] > 0.02) {
                    return AVERAGE_THRESHOLD;
                } else {
                    if (myFrequency[i] > 0.018) {
                        return SMALL_BIG_THRESHOLD;
                    } else {
                        if (myFrequency[i] > 0.01) {
                            return SMALL_AVERAGE_THRESHOLD;
                        } else {
                            if (myFrequency[i] > 0.005) {
                                return SMALL_THRESHOLD;
                            } else {
                                return VERY_SMALL_THRESHOLD;
                            }
                        }
                    }
                }
            }
        }
    }
    /**
     * Create all Candidats of Table by Table of Frequencies.
     * @param table Table with Candidat's Frequencies
     */
    public void create(TableOfFrequency table) {
        for (int i = 0; i < myAmount; i++) {
            double minFrec = myFrequency[i] * (1 - getThreshold(i));
            double maxFrec = myFrequency[i] * (1 + getThreshold(i));
            int j = 0;
            while ((j = table.findLetter(minFrec, maxFrec, myNum[i] + 1)) != -1) {
                myPossibility[i][myNum[i]] = j;
                myNum[i]++;
            }
            for (j = 0; j < this.getLine(i) - 1; j++) {
                for (int k = j+1; k < this.getLine(i); k++) {
                    if (Math.abs(myFrequency[i] - table.getFrequency((char)myPossibility[i][j]))
                            > Math.abs(myFrequency[i] - table.getFrequency((char)myPossibility[i][k]))) {
                        int temp = myPossibility[i][j];
                        myPossibility[i][j] = myPossibility[i][k];
                        myPossibility[i][k] = temp;
                    }
                }
            }
        }
    }
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < myFullAmount; i++) {
            res.append(i + ") " + myFrequency[i] + " | " + (char)myChar[i] + " -> ");
            for (int j = 0; j < myNum[i]; j++) {
                res.append((char)myPossibility[i][j] + " ");
            }
            res.append("\n");
        }
        return (res.toString());
    }
    /**
     * Return amount of variants to form ChangeTable and set percent of complited work.
     * @return Value of all variants.
     */
    public long getAmountOfVariants(ChangeTable current)
            throws DataFormatException, InterruptedException{
        buf = new ChangeTable();
        counter = 0;
        try {
            counter = rec(0,0, current);
        } catch (DataFormatException e) {
            throw e;
        }
        if (counter == 0) {
            throw new DataFormatException("Doesn't any variants for encrypt.\n" +
                    "The reason may be failure combination of etalonStatistic and Statistic of EncryptedFile");
        }
        return (counter);
    }
    private long rec (int i, int j, ChangeTable current)
            throws DataFormatException, InterruptedException{
        buf.add(myChar[i], (char)myPossibility[i][j]);
        if (this.getAmount() > i + 1) {
            if (this.getLine(i + 1) > 0) {
                for (int k = 0; k < this.getLine(i + 1); k++) {
                    int let = this.getPossibility(i + 1, k);
                    if (buf.getLetter1((char)let) == -1) {
                        rec( i + 1, k, current);
                        buf.removeLast();
                        if (MonoAttacker.stopFlag) {
                            return MonoAttacker.STOPPED;
                        }
                    }
                }
            } else {
                throw new DataFormatException("Doesn't any variants for encrypt.\n" +
                    "(PossibilityTable has empty lines)\n" +
                    "The reason may be failure combination of etalonStatistic and encryptedStatistic");
            }
        } else {
            counter++;
            if (buf.equals(current)) {
                MonoAttacker.setComplitedWork(counter);
            }
            if (MonoAttacker.stopFlag) {
                return MonoAttacker.STOPPED;
            }
            /*synchronized(AttackJDialog.stopLock) {
                if (MonoAttacker.pauseFlag) {
                    AttackJDialog.stopLock.wait();
                }
            }*/
        }
        return counter;
    }
    /**
     * Delete in PossibilityTable Candidats that are defined one-to-one from extra lines
     */
    public void reform() {
        int line;
        int k = 0;
        while ((line = getAlonePos(k)) != -1) {
            for (int i = 0; i < myAmount; i++) {
                if (i != line) {
                    for (int j = 0; j < myNum[i]; j++) {
                        if (myPossibility[i][j] == myPossibility[line][0]) {
                            deletePossibility(i,j);
                        }
                    }
                }
            }
            k = line + 1;
        }
    }
    private int getAlonePos (int line) {
        if (line >= myAmount) {
            return -1;
        }
        for (int i = line; i < myAmount; i++) {
            if (myNum[i] == 1) {
                return i;
            }
        }
        return -1;
    }
    public static void fillAlphabet() {
        for (int i = 0; i < ((int)('z'-'a') + 1); i++) {
            ALPHABET.add((char)((int)'a' + i));
        }
        ALPHABET.add(' ');

    }
    /*public static PossibilityTable createFictitious() {
        TableOfFrequency tableOfFrec = TableOfFrequency.createFictitious();
        tableOfFrec.sortFrequences();
        PossibilityTable table = new PossibilityTable(tableOfFrec);
        table.addPossibility(0, (int)','); //+
        table.addPossibility(1, (int)'f'); //+
        table.addPossibility(2, (int)'u'); //+
        table.addPossibility(3, (int)'u');
        table.addPossibility(3, (int)'b'); //+
        table.addPossibility(4, (int)'p'); //+
        table.addPossibility(4, (int)'o');
        table.addPossibility(5, (int)'p');
        table.addPossibility(5, (int)'o'); //+
        table.addPossibility(5, (int)'j');
        table.addPossibility(6, (int)'p');
        table.addPossibility(6, (int)'o');
        table.addPossibility(6, (int)'j'); //+
        table.addPossibility(7, (int)'t'); //+
        table.addPossibility(8, (int)'s'); //+
        table.addPossibility(9, (int)'i'); //+
        table.addPossibility(10, (int)'e'); //+
        table.addPossibility(11, (int)'d');
        table.addPossibility(11, (int)'m'); //+
        table.addPossibility(12, (int)'d'); //+
        table.addPossibility(12, (int)'m');
        table.addPossibility(13, (int)'n'); //+
        table.addPossibility(14, (int)'v'); //+
        table.addPossibility(15, (int)'g'); //+
        table.addPossibility(16, (int)'q'); //+
        table.addPossibility(17, (int)'x'); //+
        table.addPossibility(18, (int)'.'); //+
        table.addPossibility(19, (int)'z'); //+
        table.addPossibility(20, (int)'h'); //+
        table.addPossibility(21, (int)'c'); //+
        table.addPossibility(22, (int)'y');
        table.addPossibility(22, (int)'w'); //+
        table.addPossibility(23, (int)'w');
        table.addPossibility(23, (int)'a');
        table.addPossibility(23, (int)'r');
        table.addPossibility(23, (int)' '); //+
        table.addPossibility(24, (int)'y');
        table.addPossibility(24, (int)'l'); //+
        table.addPossibility(24, (int)'r');
        table.addPossibility(25, (int)'l');
        table.addPossibility(25, (int)'y'); //+
        table.addPossibility(25, (int)'r');
        table.addPossibility(26, (int)'r');
        table.addPossibility(26, (int)'k'); //+
        table.addPossibility(27, (int)'r'); //+
        table.addPossibility(27, (int)'k');
        table.addPossibility(27, (int)'a');
        table.addPossibility(28, (int)'a'); //+
        return table;
    }*/
            
}